//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <_hal.h>
#include <mantle.h>
#include "common.h"
#include "exception.h"

DECL_ASMLINKAGE void DivideErrorHandler();
DECL_ASMLINKAGE void DebugHandler();
DECL_ASMLINKAGE void NMIHandler();
DECL_ASMLINKAGE void BreakpointHandler();
DECL_ASMLINKAGE void OverflowHandler();
DECL_ASMLINKAGE void BoundRangeExceededHandler();
DECL_ASMLINKAGE void InvalidOpcodeHandler();
DECL_ASMLINKAGE void DeviceNotAvailableHandler();
DECL_ASMLINKAGE void DoubleFaultHandler();
DECL_ASMLINKAGE void CoprocessorSegmentOverrunHandler();
DECL_ASMLINKAGE void InvalidTSSHandler();
DECL_ASMLINKAGE void SegmentNotPresentHandler();
DECL_ASMLINKAGE void StackFaultHandler();
DECL_ASMLINKAGE void GeneralProtectionHandler();
DECL_ASMLINKAGE void PageFaultHandler();
DECL_ASMLINKAGE void FloatingPointErrorHandler();
DECL_ASMLINKAGE void AlignmentCheckHandler();

EXTERN DECL_CODEINIT void CDECL InitExceptions()
{
#define SET_EXCEPTION_HANDLER(kind, exception) \
    Set##kind##Gate(ExceptionVector_##exception, \
        (virtaddr_t)&exception##Handler)

    SET_EXCEPTION_HANDLER(Trap,         DivideError);
    SET_EXCEPTION_HANDLER(Trap,         Debug);
    SET_EXCEPTION_HANDLER(Interrupt,    NMI);
    SET_EXCEPTION_HANDLER(System,       Breakpoint);
    SET_EXCEPTION_HANDLER(System,       Overflow);
    SET_EXCEPTION_HANDLER(System,       BoundRangeExceeded);
    SET_EXCEPTION_HANDLER(Trap,         InvalidOpcode);
    SET_EXCEPTION_HANDLER(Trap,         DeviceNotAvailable);
    SET_EXCEPTION_HANDLER(Trap,         DoubleFault);
    SET_EXCEPTION_HANDLER(Trap,         CoprocessorSegmentOverrun);
    SET_EXCEPTION_HANDLER(Trap,         InvalidTSS);
    SET_EXCEPTION_HANDLER(Trap,         SegmentNotPresent);
    SET_EXCEPTION_HANDLER(Trap,         StackFault);
    SET_EXCEPTION_HANDLER(Trap,         GeneralProtection);
    SET_EXCEPTION_HANDLER(Trap,         PageFault);
    SET_EXCEPTION_HANDLER(Trap,         FloatingPointError);
    SET_EXCEPTION_HANDLER(Trap,         AlignmentCheck);
}

EXTERN void __ExceptionHandlers()
{
#define DEFINE_EXCEPTION_HANDLER(exception) \
    ASM(".global _"#exception"Handler;"     \
        ".align;"                           \
        "_"#exception"Handler:"             \
        "push   $0;"                        \
        "push   %0;"                        \
        "jmp    _CommonExceptionHandler;"   \
        : :"i"(ExceptionVector_##exception))

#define DEFINE_EXCEPTION_HANDLER_WITH_ERRORCODE(exception)  \
    ASM(".global _"#exception"Handler;"                     \
        ".align;"                                           \
        "_"#exception"Handler:"                             \
        "push   %0;"                                        \
        "jmp    _CommonExceptionHandler;"                   \
        : :"i"(ExceptionVector_##exception))

    DEFINE_EXCEPTION_HANDLER(DivideError);
    DEFINE_EXCEPTION_HANDLER(Debug);
    DEFINE_EXCEPTION_HANDLER(NMI);
    DEFINE_EXCEPTION_HANDLER(Breakpoint);
    DEFINE_EXCEPTION_HANDLER(Overflow);
    DEFINE_EXCEPTION_HANDLER(BoundRangeExceeded);
    DEFINE_EXCEPTION_HANDLER(InvalidOpcode);
    DEFINE_EXCEPTION_HANDLER(DeviceNotAvailable);

    // error code: zero
    DEFINE_EXCEPTION_HANDLER_WITH_ERRORCODE(DoubleFault);

    DEFINE_EXCEPTION_HANDLER(CoprocessorSegmentOverrun);

    // An error code containing the segment selector index for the segment
    // descriptor that caused the violation is pushed onto the stack of the
    // exception handler. If the EXT flag is set, it indicates that
    // the exception was caused by an event external to the currently
    // running program (for example, if an external interrupt handler using
    // a task gate attempted a task switch to an invalid TSS).
    DEFINE_EXCEPTION_HANDLER_WITH_ERRORCODE(InvalidTSS);

    // An error code containing the segment selector index for the segment
    // descriptor that caused the violation is pushed onto the stack of the
    // exception handler. If the EXT flag is set, it indicates that
    // the exception resulted from an external event (NMI or INTR) that
    // caused an interrupt, which subsequently referenced a not-present
    // segment. The IDT flag is set if the error code refers to an
    // IDT DECL_ENTRY (e.g., an INT instruction referencing a not-present gate).
    DEFINE_EXCEPTION_HANDLER_WITH_ERRORCODE(SegmentNotPresent);

    // If the exception is caused by a not-present stack segment or by
    // overflow of the new stack during an inter-privilege-level call,
    // the error code contains a segment selector for the segment that
    // caused the exception. Here, the exception handler can test the
    // present flag in the segment descriptor pointed to by the segment
    // selector to determine the cause of the exception. For a normal limit
    // violation (on a stack segment already in use) the error code is set to 0.
    DEFINE_EXCEPTION_HANDLER_WITH_ERRORCODE(StackFault);

    // The processor pushes an error code onto the exception handler's stack.
    // If the fault condition was detected while loading a segment descriptor,
    // the error code contains a segment selector to or IDT vector number for
    // the descriptor; otherwise, the error code is 0. The source of the
    // selector in an error code may be any of the following:
    //   * An operand of the instruction.
    //   * A selector from a gate which is the operand of the instruction.
    //   * A selector from a TSS involved in a task switch.
    DEFINE_EXCEPTION_HANDLER_WITH_ERRORCODE(GeneralProtection);

    // The error code for a page fault has a format different from that
    // for other exceptions.
    DEFINE_EXCEPTION_HANDLER_WITH_ERRORCODE(PageFault);

    DEFINE_EXCEPTION_HANDLER(FloatingPointError);

    // Error Code: always zero
    DEFINE_EXCEPTION_HANDLER_WITH_ERRORCODE(AlignmentCheck);

DECL_ASMENTRY(CommonExceptionHandler)

    __SAVE_SOFTWARE_REGS_WITHOUT_ES();

    ASM("movl   32(%esp), %ecx;"    // get vector from InterruptContext::es
        "movl   %es, 32(%esp)");    // save es to InterruptContext::es

    __SETUP_KERNEL_REGS();

    ASM("pushl  %ecx");             // push vector

    ASM("call   _HandleException;");

    ASM("addl   $4, %esp;");        // pop vector

DECL_ASMENTRY(ReturnFromException)

    ASM("jmp    _ReturnFromInterrupt;");
}

EXTERN void CDECL DebuggerHandler(
                    vector_t vector,
                    InterruptContext *pContext,
                    uint_t uPageFaultAddr);

DECL_ASMLINKAGE void CDECL HandleException(
    vector_t vector, InterruptContext context)
{
    uint_t uPageFaultAddr = 0;

    switch (vector) {
    case ExceptionVector_DeviceNotAvailable:
#ifndef _RELEASE
        if (IS_IN_KERNEL_MODE(&context)) break;
#endif // _RELEASE
        GetCurrentThread()->m_hcontext.RestoreFPU();
        return;

    case ExceptionVector_PageFault:
        register CProcess *pCurrentProcess = GetCurrentProcess();
        uPageFaultAddr = (uint_t)GetPageFaultAddress();

        if (NULL != pCurrentProcess) {
            ECode ec = pCurrentProcess->PageFault(
                    uPageFaultAddr,
                    context._esp,
                    IS_IN_KERNEL_MODE(&context),
                    PageFault_ErrorCode_WR & context.u.errorcode);
            if (SUCCEEDED(ec)) return;
        }
        break;
    }

    if (!IS_IN_KERNEL_MODE(&context)) {
        assert(GetCurrentProcess());
        UInt32 reason = DEBUGGEE_EXCEPTION;
        UInt32 handle[2];

        handle[0] = vector;
        handle[1] = context.eip - 1;

        if (TryToSendReceiveDebugMsg(reason, (UInt32)handle))
            return;
    }

    DebuggerHandler(vector, &context, uPageFaultAddr);
}
